The Collect Function

The system calls each application s Collect function whenever a performance monitor program calls the RegQueryValueEx1J.0ZZ. function to collect performance data. This function returns the application s performance data.

Use the following function prototype for your Collect function.

DWORD WINAPI CollectPerformanceData(

    LPWSTR lpwszValue,

    LPVOID *lppData,

    LPDWORD lpcbBytes,

    LPDWORD lpcObjectTypes);

 

The name CollectPerformanceData is a place-holder for an application-defined name.

Argument

Description

lpwszValue

Points to a string specified by the performance monitor program in a call to the RegQueryValueEx function. The string uses one of the formats described in Retrieving Selected DataWQKREC.

LppData

On input, points to a pointer to the location where the data is to be placed. On successful exit, set *lppData to the next byte in the buffer available for data, such as one byte past the last byte of your data. The data returned must be a multiple of a DWORD in length. It must conform to the PERF_OBJECT_TYPE262F_HJ structure, unless this is a collection from a foreign computer. If foreign, any PERF_OBJECT_TYPE structures returned must be preceded by a PERF_DATA_BLOCKWDPEVC structure for the foreign computer. If the Collect function fails for any reason, leave *lppData unchanged.

LpcbBytes

On input, points to a 32-bit value that specifies the size, in bytes, of the lppData buffer. On successful exit, set *lpcbBytes to the size, in bytes, of the data written to the lppData buffer. This must be a multiple of sizeof(DWORD) (a multiple of 4). If the Collect function fails for any reason, set *lpcbBytes to zero.

lpcObjectTypes

On successful exit, set *lpcObjectTypes to the number of object type definitions being returned. If the Collect function fails for any reason, it should set *lpcObjectTypes to zero.

 

If the requested data specified by lpwszValue does not correspond to any of the object indexes or foreign computers supported by your program, leave *lppData unchanged, and set *lpcbBytes and *lpcObjectTypes both to zero. This indicates that no data is returned. If your data collection is time-consuming, you should only respond to specific requests and Costly requests. You should also lower the priority of the thread collecting the data, so that it does not adversely affect system performance.

The Collect function must return one of the values shown in the following table.

Return value

Description

ERROR_MORE_DATA

Indicates that the size of the lppData buffer as specified by *lpcbBytes is not large enough to store the data to be returned. In this case, leave *lppData unchanged, and set *lpcbBytes and *lpcObjectTypes to zero. No attempt is made to indicate the required buffer size, because this may change before the next call.

ERROR_SUCCESS

Return this value in all other cases, even if no data is returned or an error occurs. To report errors other than insufficient buffer size, use the system event log, but do not flood the event log with errors on every data collection operation.

 

To provide more information to the user, the Collect function should write any error that prevents the function from completing successfully in the system event log. For more information, see Event LoggingEJMIUI.

If the application collecting the data is running on another machine (remotely), then the extensible counter functions are called in the context of the Winlogon process, which handles the server side of the remote connection. This distinction is important when troubleshooting problems that only occur remotely. The Winlogon process may access the functions using multiple threads, usually one per remote connection. This means that the Open procedure can be called more than once. You should handle this in your code accordingly, so that initialization tasks are not performed more than necessary. The Collect function may also be called concurrently by multiple threads, so you should be careful how you initialize and use temporary data. Static variables should only be used if the data is intended to be shared across threads.

The connection to the machine providing the data should be tested each time data is requested. If the connection cannot be made, you need to return some sort of indication. One suggestion is to provide a Status counter for the object that indicates whether a valid connection exists. When the connection does not exist, set the Status counter and return the last set of valid data.

For foreign computer interfaces, the opening of a channel to the foreign computer must be done in the Collect function because the computer name is not provided to the Open function. The performance DLL should save a handle to the foreign computer to avoid reconnecting on each data collection call, which would significantly slow down system performance.

Once you get the data for a foreign computer, construct a PERF_DATA_BLOCKWDPEVC as the first thing in *lppData, so that your application can return the data. If the system you are measuring does not provide the requested information, provide a reasonable value. You should use values from the local system for time and counter frequency if they are not provided remotely. You may need to use the PerfTime and .PerfFreq members of the PERF_OBJECT_TYPE262F_HJ structure for some counters as well.

Other things that you might do in the Collect include:

    Validate the shared memory pointer and check that the Open function had successfully completed. If the Open function failed, it should have already logged an event, so the Collect function need not report this error.

    Determine the data request type, as described in Retrieving Selected DataWQKREC. If the request is for Foreign data, it is ignored and the Collect function should return no data. If the request is for a specific object or list of objects, search for its index in the list of object and counter indices. If you do not find a match, the data from this object is not desired, so the function should return no data.

    Request ownership of the mutex for the shared memory object. Wait to get access to the data in the shared memory block.

    Estimate the size of the data to make sure there is enough room for the data in the buffer. If the estimated size is larger than the available size, return a status of ERROR_MORE_DATA. The system passes this error to the thread that issued the call to RegQueryValueEx to request the data.

    Copy the performance data from the shared memory to the performance data structure to be returned.

    When all data has been transferred from the shared memory file to the performance data buffer, release the shared memory mutex and update the pointers and counter fields. The updating of the pointers and counter fields is very important, to prevent an access violation from the application, monitor, or system due to misleading buffer length information.

 

After the collect procedure returns successfully, the system performs the following tests to try and catch logic errors in the collect procedure. The first test to fail will generate an event log message and, in most cases, the data is discarded to prevent any further problems due to invalid pointers. These tests are normally enabled though they can be disabled by changing a registry variable value as described below:

HKEY_LOCAL_MACHINE

    \Software

        \Microsoft

            \Windows NT

                \CurrentVersion

                    \Perflib

                        ExtCounterTestLevel = Test_Level

The Test_Level is a REG_DWORD that specifies the test level. Test level 1 requests all tests. Test level 2 requests basic tests. Test level 3 requests no tests. The default is test level 1.

The basic tests performed on the data buffer are as follows:

    Is the BytesLeft return value consistent with the returned pointer? The returned value of the BytesLeft argument to the Collect procedure is compared to the returned buffer pointer. If all is consistent, the BytesLeft value added to the original buffer pointer passed in to the Collect procedure should be the same as the buffer pointer returned by the procedure. If they are not the same a warning message is logged and the BytesLeft parameter is replaced by the value computed by finding the difference between the buffer pointer after the function call and the buffer pointer before the function call. This is somewhat risky, in that it assumes the BytesLeft value is assumed to be the incorrect one, when in fact it could be that the buffer pointer is the incorrect one.

    Has the returned buffer pointer exceeded the allocated buffer extent? The actual buffer passed to the collect function is allocated specifically for that function call by the performance library and contains a 1K-Byte Guard Page above and below the size indicated by the remaining size of the user s buffer. A separate buffer is used to allow testing of the extensible counter DLL s returned data without corrupting the caller s buffer. If the returned buffer pointer (the pointer to the next byte after this object s data) exceeds the size of this buffer (not including guard pages) then the buffer is assumed to be invalid and discarded since it is too large to be copied into the caller s buffer. This test consists of two parts. If the buffer pointer exceeds the end of the buffer, but not the end of the guard page then a buffer overrun error is logged. If the buffer pointer is past the end of the guard page, then a heap error is logged since the heap that the buffer was allocated from could have been corrupted causing other memory errors.

    Are the guard pages corrupted? The 1K byte Guard Pages above and below the block of memory passed to the collect procedure are initialized with a data pattern before the collect function is called. This data pattern is checked after the collect procedure returns. If any discrepancy is detected a buffer overrun  or other memory error is assumed and the buffer is discarded.

 

The following tests are performed only if test level 1 is used.

    Test object TotalByteLength field consistency. This test walks the object(s) returned by the extensible counter to see if the sum of the length of all the objects returned is the same as the value of the size of the returned buffer. Since the Collect function generally returns one or more object structures (including the instance definitions, and counter definitions and data) the sum of each object s length should be the same as the bytes returned. A failure here can indicate the object is not computing the value of the TotalByteLength field correctly. This can cause an application program using the data to fail by having it get lost in the data structures.

    Test Instance ByteLength field consistency. This test is similar to the test above. The test walks the list of instances in each object that returns multiple instances, to see if the next object or end of buffer follows the last instance. As above, if an inconsistency is detected, the buffer is discarded to prevent the application program from crashing due to a lost pointer.